<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>视频帧抽取工具 - 精确帧率</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
        .container {
            display: flex;
            flex-direction: column;
            gap: 20px;
        }
        .video-container, .canvas-container {
            display: flex;
            flex-direction: column;
            gap: 10px;
        }
        video, canvas {
            max-width: 100%;
            background-color: #000;
        }
        .controls {
            display: flex;
            gap: 10px;
            flex-wrap: wrap;
            align-items: center;
        }
        button {
            padding: 8px 16px;
            cursor: pointer;
        }
        .frame-info {
            margin-top: 10px;
        }
        .info-text {
            margin: 5px 0;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>视频帧抽取工具 - 精确帧率</h1>
        
        <div class="video-container">
            <h2>视频源</h2>
            <input type="file" id="videoInput" accept="video/*">
            <video id="videoElement" controls></video>
        </div>
        
        <div class="info-text">视频信息: <span id="videoInfo">未加载</span></div>
        <div class="info-text">检测到的帧率(FPS): <span id="fpsInfo">未知</span></div>
        
        <div class="controls">
            <button id="prevFrame">上一帧</button>
            <button id="nextFrame">下一帧</button>
            <button id="extractFrame">抽取当前帧</button>
            <input type="number" id="frameNumber" placeholder="帧号" min="0">
            <button id="goToFrame">跳转到帧</button>
            <button id="extractRange">抽取帧范围</button>
        </div>
        
        <div class="frame-info">
            当前帧: <span id="currentFrame">0</span> / 
            总帧数: <span id="totalFrames">0</span>
        </div>
        
        <div class="canvas-container">
            <h2>抽取的帧</h2>
            <canvas id="canvasElement"></canvas>
            <a id="downloadLink" style="display:none;">下载帧</a>
            <div id="batchDownload" style="display:none; margin-top:10px;"></div>
        </div>
    </div>

    <script>
        document.addEventListener('DOMContentLoaded', function() {
            const videoInput = document.getElementById('videoInput');
            const videoElement = document.getElementById('videoElement');
            const canvasElement = document.getElementById('canvasElement');
            const ctx = canvasElement.getContext('2d');
            const prevFrameBtn = document.getElementById('prevFrame');
            const nextFrameBtn = document.getElementById('nextFrame');
            const extractFrameBtn = document.getElementById('extractFrame');
            const frameNumberInput = document.getElementById('frameNumber');
            const goToFrameBtn = document.getElementById('goToFrame');
            const extractRangeBtn = document.getElementById('extractRange');
            const currentFrameSpan = document.getElementById('currentFrame');
            const totalFramesSpan = document.getElementById('totalFrames');
            const downloadLink = document.getElementById('downloadLink');
            const batchDownloadDiv = document.getElementById('batchDownload');
            const videoInfoSpan = document.getElementById('videoInfo');
            const fpsInfoSpan = document.getElementById('fpsInfo');
            
            let currentFrameIndex = 0;
            let videoDuration = 0;
            let frameRate = 0;
            let totalFrames = 0;
            let frameTimes = [];
            let isCalculatingFPS = false;
            
            // 加载视频文件
            videoInput.addEventListener('change', function(e) {
                const file = e.target.files[0];
                if (!file) return;
                
                const videoURL = URL.createObjectURL(file);
                videoElement.src = videoURL;
                
                videoElement.onloadedmetadata = function() {
                    videoDuration = videoElement.duration;
                    const resolution = `${videoElement.videoWidth}×${videoElement.videoHeight}`;
                    videoInfoSpan.textContent = `${resolution}, ${videoDuration.toFixed(2)}秒`;
                    
                    // 设置canvas尺寸与视频相同
                    canvasElement.width = videoElement.videoWidth;
                    canvasElement.height = videoElement.videoHeight;
                    
                    // 开始计算实际帧率
                    calculateActualFPS();
                };
            });
            
            // 计算实际帧率
            function calculateActualFPS() {
                if (isCalculatingFPS) return;
                isCalculatingFPS = true;
                frameTimes = [];
                
                // 先快速扫描视频以检测关键帧时间点
                videoElement.currentTime = 0;
                let lastTime = -1;
                let checkInterval = 0.1; // 检查间隔(秒)
                
                function checkFrames() {
                    if (videoElement.currentTime >= videoDuration || frameTimes.length > 100) {
                        // 扫描完成，计算帧率
                        calculateFrameRateFromTimes();
                        isCalculatingFPS = false;
                        return;
                    }
                    
                    if (Math.abs(videoElement.currentTime - lastTime) > 0.001) {
                        frameTimes.push(videoElement.currentTime);
                        lastTime = videoElement.currentTime;
                    }
                    
                    videoElement.currentTime = Math.min(
                        videoElement.currentTime + checkInterval,
                        videoDuration
                    );
                }
                
                videoElement.onseeked = checkFrames;
                checkFrames();
            }
            
            // 根据收集的时间点计算帧率
            function calculateFrameRateFromTimes() {
                if (frameTimes.length < 2) {
                    // 无法计算，使用默认值
                    frameRate = 30;
                    fpsInfoSpan.textContent = `默认 ${frameRate} (无法检测)`;
                    initializeFrameControls();
                    return;
                }
                
                // 计算平均帧间隔
                let totalInterval = 0;
                for (let i = 1; i < frameTimes.length; i++) {
                    totalInterval += frameTimes[i] - frameTimes[i - 1];
                }
                const avgInterval = totalInterval / (frameTimes.length - 1);
                frameRate = Math.round(1 / avgInterval);
                
                fpsInfoSpan.textContent = frameRate;
                initializeFrameControls();
            }
            
            // 初始化帧控制
            function initializeFrameControls() {
                totalFrames = Math.floor(videoDuration * frameRate);
                totalFramesSpan.textContent = totalFrames;
                currentFrameIndex = 0;
                currentFrameSpan.textContent = currentFrameIndex;
                frameNumberInput.value = currentFrameIndex;
            }
            
            // 上一帧
            prevFrameBtn.addEventListener('click', function() {
                if (currentFrameIndex > 0) {
                    currentFrameIndex--;
                    seekToFrame(currentFrameIndex);
                }
            });
            
            // 下一帧
            nextFrameBtn.addEventListener('click', function() {
                if (currentFrameIndex < totalFrames - 1) {
                    currentFrameIndex++;
                    seekToFrame(currentFrameIndex);
                }
            });
            
            // 跳转到指定帧
            goToFrameBtn.addEventListener('click', function() {
                const frameNum = parseInt(frameNumberInput.value);
                if (!isNaN(frameNum) && frameNum >= 0 && frameNum < totalFrames) {
                    currentFrameIndex = frameNum;
                    seekToFrame(currentFrameIndex);
                }
            });
            
            // 抽取当前帧
            extractFrameBtn.addEventListener('click', function() {
                if (videoElement.readyState === 0) return;
                
                // 绘制当前帧到canvas
                ctx.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height);
                
                // 创建下载链接
                const dataURL = canvasElement.toDataURL('image/png');
                downloadLink.href = dataURL;
                downloadLink.download = `frame_${currentFrameIndex}.png`;
                downloadLink.style.display = 'block';
                downloadLink.textContent = `下载帧 ${currentFrameIndex}`;
            });
            
            // 抽取帧范围
            extractRangeBtn.addEventListener('click', function() {
                const startFrame = prompt("输入起始帧号:", "0");
                const endFrame = prompt("输入结束帧号:", String(totalFrames - 1));
                
                if (startFrame === null || endFrame === null) return;
                
                const start = Math.max(0, parseInt(startFrame));
                const end = Math.min(totalFrames - 1, parseInt(endFrame));
                
                if (isNaN(start) || isNaN(end) || start > end) {
                    alert("无效的帧范围");
                    return;
                }
                
                batchDownloadDiv.innerHTML = "正在准备帧...";
                batchDownloadDiv.style.display = "block";
                
                // 使用setTimeout让UI更新
                setTimeout(() => {
                    extractFrameRange(start, end);
                }, 100);
            });
            
            // 抽取帧范围
            function extractFrameRange(start, end) {
                batchDownloadDiv.innerHTML = "";
                const step = Math.max(1, Math.floor((end - start) / 100)); // 限制最多100帧
                
                let tempCanvas = document.createElement('canvas');
                tempCanvas.width = canvasElement.width;
                tempCanvas.height = canvasElement.height;
                let tempCtx = tempCanvas.getContext('2d');
                
                let current = start;
                
                function extractNextFrame() {
                    if (current > end) {
                        batchDownloadDiv.innerHTML += "<br>所有帧已准备好!";
                        return;
                    }
                    
                    seekToFrameForExtract(current, function() {
                        tempCtx.drawImage(videoElement, 0, 0, tempCanvas.width, tempCanvas.height);
                        const dataURL = tempCanvas.toDataURL('image/png');
                        
                        const link = document.createElement('a');
                        link.href = dataURL;
                        link.download = `frame_${current}.png`;
                        link.textContent = `下载帧 ${current}`;
                        link.style.display = "block";
                        link.style.margin = "5px 0";
                        
                        batchDownloadDiv.appendChild(link);
                        batchDownloadDiv.appendChild(document.createElement('br'));
                        
                        current += step;
                        setTimeout(extractNextFrame, 0);
                    });
                }
                
                extractNextFrame();
            }
            
            // 为抽取帧而跳转
            function seekToFrameForExtract(frameIndex, callback) {
                const time = frameIndex / frameRate;
                videoElement.currentTime = time;
                
                let seekCheck = function() {
                    if (Math.abs(videoElement.currentTime - time) < 0.1) {
                        videoElement.removeEventListener('seeked', seekCheck);
                        callback();
                    }
                };
                
                videoElement.addEventListener('seeked', seekCheck);
            }
            
            // 跳转到指定帧
            function seekToFrame(frameIndex) {
                if (!videoDuration) return;
                
                // 计算帧对应的时间点
                const time = frameIndex / frameRate;
                videoElement.currentTime = time;
                
                // 更新当前帧显示
                currentFrameSpan.textContent = frameIndex;
                frameNumberInput.value = frameIndex;
            }
            
            // 当视频时间更新时，更新canvas显示
            videoElement.addEventListener('timeupdate', function() {
                ctx.drawImage(videoElement, 0, 0, canvasElement.width, canvasElement.height);
            });
        });
    </script>
</body>
</html>